<

UIKit開発者向けのFlutter

UIKitの使用経験のあるiOS開発者 Flutterを使ってモバイルアプリを書きたい人 このガイドを確認する必要があります。 既存の UIKit の知識を Flutter に適用する方法について説明します。

Flutter はクロスプラットフォーム アプリケーションを構築するためのフレームワークです Dart プログラミング言語を使用します。 Dart を使用したプログラミングのいくつかの違いを理解するには Swift を使用したプログラミングについては、を参照してください。Swift 開発者として Dart を学ぶとSwift 開発者向けの Flutter 同時実行性

iOS および UIKit の知識と経験 Flutter を使用して構築する場合、これらは非常に価値があります。 Flutter は多くの調整も行います iOS 上で実行するときのアプリの動作に影響を与えます。 その方法については、を参照してください。プラットフォームの適応

このドキュメントは、飛び回ってクックブックとして使用できます。 自分のニーズに最も関連する質問を見つけます。

概要

ビューとウィジェット

UIKit では、UI で作成するもののほとんどはビュー オブジェクトを使用して行われます。 の例ですUIViewクラス。 これらは他のもののコンテナとして機能できます。UIViewクラス、 レイアウトを形成します。

Flutter では、おおよそ次のものに相当します。UIViewですWidget。 ウィジェットは iOS ビューに正確にマッピングされません。 Flutter の仕組みに慣れていくうちに これらは「UI を宣言して構築する方法」と考えることができます。

ただし、これらにはいくつかの違いがあります。UIView。 まず、ウィジェットの有効期間は異なります。ウィジェットは不変です。 変更する必要があるまでのみ存在します。 ウィジェットまたはその状態が変化するたびに、 Flutter のフレームワークは、ウィジェット インスタンスの新しいツリーを作成します。 比較すると、UIKit ビューは変更されても再作成されません。 むしろ、それは一度描画される変更可能なエンティティです を使用して無効化されるまで再描画しませんsetNeedsDisplay()

さらに、それとは異なり、UIView, Flutterのウィジェットは軽量なので、 部分的にはそれらの不変性によるものです。 それらはビューそのものではないため、 直接何かを描いているわけではありませんが、 むしろ、UI とそのセマンティクスの説明です。 これらは内部で実際のビュー オブジェクトに「インフレート」されます。

flutterには以下が含まれます材料成分図書館。 これらは、マテリアル デザインのガイドライン。 マテリアル デザインは柔軟なデザイン システムですすべてのプラットフォーム向けに最適化、iOSを含む。

しかし、Flutter は柔軟で表現力が十分にあります あらゆるデザイン言語を実装します。 iOS では、クパチーノのウィジェット次のようなインターフェイスを生成しますAppleのiOSデザイン言語。

ウィジェットの更新

UIKit でビューを更新するには、ビューを直接変更します。 Flutter では、ウィジェットは不変であり、直接更新されません。 代わりに、ウィジェットの状態を操作する必要があります。

ここで、ステートフル ウィジェットとステートレス ウィジェットの概念が登場します。 が入ってきます。StatelessWidgetまさにその通りです 同様に、状態が関連付けられていないウィジェットです。

StatelessWidgetsユーザー インターフェイスの一部である場合に便利です。 説明は初期設定以外には依存しません ウィジェット内の情報。

たとえば、UIKit の場合、これはUIImageViewあなたのロゴをimage。実行時にロゴが変化しない場合は、 使うStatelessWidget flutterで。

受信したデータに基づいて動的にUIを変更したい場合 HTTP 呼び出しを行った後、StatefulWidget。 HTTP 呼び出しが完了したら、Flutter フレームワークに通知します。 そのウィジェットのStateが更新されるため、UI を更新できます。

ステートレスとステートレスの重要な違い ステートフルウィジェットとは、StatefulWidgetは持っていますState物体 状態データを保存し、ツリーの再構築間でそれを引き継ぎます。 だから失われません。

迷った場合は、次のルールを思い出してください。 ウィジェットが外部で変更された場合build方法 (実行時のユーザー操作などのため)、 それはステートフルです。 ウィジェットが一度構築されると決して変更されない場合、そのウィジェットはステートレスになります。 ただし、ウィジェットがステートフルであっても、それを含む親ウィジェットは 自身がそれらの変更に反応していない場合は、ステートレスのままである可​​能性があります (または他の入力)。

次の例は、StatelessWidget。 共通のStatelessWidgetそれはTextウィジェット。 の実装を見てみると、Textウィジェット、 サブクラスが見つかりますStatelessWidget

Text(
  'I like Flutter!',
  style: TextStyle(fontWeight: FontWeight.bold),
);

上記のコードを見ると、次のことに気づくかもしれません。Textウィジェット 明示的な状態は伴いません。渡されたものをレンダリングします コンストラクターだけです。

しかし、「I Like Flutter」を動的に変化させたい場合はどうすればよいでしょうか。 たとえば、FloatingActionButton?

これを実現するには、TextのウィジェットStatefulWidgetと ユーザーがボタンをクリックすると更新されます。

例えば:


class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default placeholder text
  String textToShow = 'I Like Flutter';

  void _updateText() {
    setState(() {
      // Update the text
      textToShow = 'Flutter is Awesome!';
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Sample App')),
      body: Center(child: Text(textToShow)),
      floatingActionButton: FloatingActionButton(
        onPressed: _updateText,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}

ウィジェットのレイアウト

UIKit では、Storyboard ファイルを使用する場合があります。 ビューを整理し、制約を設定するには、 または、ビュー コントローラーでプログラム的に制約を設定することもできます。 Flutter では、ウィジェット ツリーを構成してコードでレイアウトを宣言します。

次の例は、パディング付きの単純なウィジェットを表示する方法を示しています。

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(title: const Text('Sample App')),
    body: Center(
      child: CupertinoButton(
        onPressed: () {},
        padding: const EdgeInsets.only(left: 10, right: 10),
        child: const Text('Hello'),
      ),
    ),
  );
}

任意のウィジェットにパディングを追加できます。 これは、iOS の制約の機能を模倣します。

Flutter が提供するレイアウトを表示できます の中にウィジェットカタログ

ウィジェットの削除

UIKit では、次のように呼び出します。addSubview()親に関して、 またremoveFromSuperview()子ビューで 子ビューを動的に追加または削除します。 Flutterではウィジェットは不変なので、 に直接相当するものはありませんaddSubview()。 代わりに、関数を親に渡すことができます ウィジェットを返し、その子の作成を制御します ブール値フラグを使用します。

次の例は、2 つのウィジェットを切り替える方法を示しています。 ユーザーがクリックすると、FloatingActionButton:

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  // Default value for toggle.
  bool toggle = true;

  void _toggle() {
    setState(() {
      toggle = !toggle;
    });
  }

  Widget _getToggleChild() {
    if (toggle) {
      return const Text('Toggle One');
    }

    return CupertinoButton(
      onPressed: () {},
      child: const Text('Toggle Two'),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: Center(
        child: _getToggleChild(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _toggle,
        tooltip: 'Update Text',
        child: const Icon(Icons.update),
      ),
    );
  }
}

アニメーション

UIKit では、次のメソッドを呼び出してアニメーションを作成します。animate(withDuration:animations:)ビューのメソッド。 Flutterではアニメーションライブラリを使用します。 アニメーション化されたウィジェット内にウィジェットをラップします。

Flutter では、AnimationController、これはAnimation<double>アニメーションを一時停止、シーク、停止、反転できます。 それには、Tickervsync が発生したときに通知します 線形補間を生成します 実行中の各フレームの 0 と 1 の間。 次に、1 つ以上を作成しますAnimations をコントローラーに取り付けます。

たとえば、次のように使用できます。CurvedAnimation補間された曲線に沿ってアニメーションを実装します。 この意味で、コントローラーは「マスター」ソースです。 アニメの進捗状況 そしてそのCurvedAnimation曲線を計算します これは、コントローラーのデフォルトの直線運動を置き換えます。 ウィジェットと同様に、Flutter のアニメーションは合成で動作します。

ウィジェット ツリーを構築するときに、Animationアニメーションに ウィジェットの不透明度などのプロパティFadeTransition、 そしてコントローラーにアニメーションを開始するように指示します。

次の例は、FadeTransitionそれか を押すと、ウィジェットがロゴにフェードインします。FloatingActionButton:

import 'package:flutter/material.dart';

class SampleApp extends StatelessWidget {
  // This widget is the root of your application.
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Fade Demo',
      home: MyFadeTest(title: 'Fade Demo'),
    );
  }
}

class MyFadeTest extends StatefulWidget {
  const MyFadeTest({super.key, required this.title});

  final String title;

  @override
  State<MyFadeTest> createState() => _MyFadeTest();
}

class _MyFadeTest extends State<MyFadeTest>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      duration: const Duration(milliseconds: 2000),
      vsync: this,
    );
    curve = CurvedAnimation(
      parent: controller,
      curve: Curves.easeIn,
    );
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.title)),
      body: Center(
        child: FadeTransition(
          opacity: curve,
          child: const FlutterLogo(size: 100),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          controller.forward();
        },
        tooltip: 'Fade',
        child: const Icon(Icons.brush),
      ),
    );
  }
}

詳細については、を参照してください。アニメーションとモーションのウィジェット、 のアニメーションのチュートリアル、 そしてそのアニメーションの概要

画面上に描画する

UIKitでは、次のように使用します。CoreGraphics線や形を描くには 画面。 Flutter には、Canvasクラス、 描画に役立つ他の 2 つのクラスもあります。CustomPaintCustomPainter、 後者は、キャンバスに描画するアルゴリズムを実装します。

Flutter で署名ペインタを実装する方法を学ぶには、 コリンの答えを参照してくださいスタックオーバーフロー。

import 'package:flutter/material.dart';

void main() => runApp(const MaterialApp(home: DemoApp()));

class DemoApp extends StatelessWidget {
  const DemoApp({super.key});

  @override
  Widget build(BuildContext context) => const Scaffold(body: Signature());
}

class Signature extends StatefulWidget {
  const Signature({super.key});

  @override
  State<Signature> createState() => SignatureState();
}

class SignatureState extends State<Signature> {
  List<Offset?> _points = <Offset?>[];
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onPanUpdate: (details) {
        setState(() {
          RenderBox? referenceBox = context.findRenderObject() as RenderBox;
          Offset localPosition =
              referenceBox.globalToLocal(details.globalPosition);
          _points = List.from(_points)..add(localPosition);
        });
      },
      onPanEnd: (details) => _points.add(null),
      child:
          CustomPaint(
        painter: SignaturePainter(_points),
        size: Size.infinite,
      ),
    );
  }
}

class SignaturePainter extends CustomPainter {
  SignaturePainter(this.points);

  final List<Offset?> points;

  @override
  void paint(Canvas canvas, Size size) {
    final Paint paint = Paint()
      ..color = Colors.black
      ..strokeCap = StrokeCap.round
      ..strokeWidth = 5;
    for (int i = 0; i < points.length - 1; i++) {
      if (points[i] != null && points[i + 1] != null) {
        canvas.drawLine(points[i]!, points[i + 1]!, paint);
      }
    }
  }

  @override
  bool shouldRepaint(SignaturePainter oldDelegate) =>
      oldDelegate.points != points;
}

ウィジェットの不透明度

UIKit では、すべてが.opacityまた.alpha。 Flutter では、ほとんどの場合、次のことが必要です。 ウィジェットをラップするOpacityこれを実現するためのウィジェット。

カスタムウィジェット

UIKit では通常、サブクラス化します。UIView、または既存のビューを使用します。 目的の動作を実現するメソッドをオーバーライドして実装します。 Flutter では、次のようにカスタム ウィジェットを構築します。作曲する小さいウィジェット (拡張する代わりに)。

たとえば、どうやって構築しますかCustomButtonそれはコンストラクターでラベルを受け取りますか? を構成する CustomButton を作成します。ElevatedButtonラベル付きで、 延長するのではなくElevatedButton:

class CustomButton extends StatelessWidget {
  const CustomButton(this.label, {super.key});

  final String label;

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () {},
      child: Text(label),
    );
  }
}

次に、使用しますCustomButton、 他の Flutter ウィジェットを使用するのと同じように:

@override
Widget build(BuildContext context) {
  return const Center(
    child: CustomButton('Hello'),
  );
}

ドキュメントのこのセクションではナビゲーションについて説明します アプリのページ間、プッシュとポップのメカニズムなど。

UIKit では、ビュー コントローラー間を移動するには、UINavigationControllerビューコントローラーのスタックを管理します 表示する。

Flutter にも同様の実装があります。 を使ってNavigatorRoutes。 あRouteアプリの「画面」または「ページ」の抽象化です。 そしてNavigatorですウィジェットルートを管理するものです。ルートはおおよそ次のようにマッピングされます。UIViewController。ナビゲーターは iOS と同様の方法で動作します。UINavigationController、それができるという点でpush()pop()ビューに移動するか、ビューから戻るかに応じてルートを決定します。

ページ間を移動するには、いくつかのオプションがあります。

  • を指定してくださいMap路線名のこと。
  • ルートに直接移動します。

次の例では、Map.

void main() {
  runApp(
    CupertinoApp(
      home: const MyAppHome(), // becomes the route named '/'
      routes: <String, WidgetBuilder>{
        '/a': (context) => const MyPage(title: 'page A'),
        '/b': (context) => const MyPage(title: 'page B'),
        '/c': (context) => const MyPage(title: 'page C'),
      },
    ),
  );
}

ルートに移動するにはpushその名前をNavigator

Navigator.of(context).pushNamed('/b');

Navigatorクラスは Flutter でルーティングを処理し、取得するために使用されます。 スタックにプッシュしたルートから返された結果。 これを行うのは、await上にいるFutureによって返されましたpush()

たとえば、locationユーザーが自分のルートを選択できるようにする 場所を指定するには、次のことを実行できます。

Object? coordinates = await Navigator.of(context).pushNamed('/location');

そして、あなたの内側でlocationユーザーがルートを選択すると、 位置、pop()結果を含むスタック:

Navigator.of(context).pop({'lat': 43.821757, 'long': -79.226392});

UIKit でユーザーを別のアプリケーションに送信するには、 特定の URL スキームを使用している場合。 システム レベルのアプリの場合、スキームはアプリによって異なります。 Flutter でこの機能を実装するには、 ネイティブ プラットフォーム統合を作成するか、既存のプラグイン、 そのようなurl_launcher

手動でポップバックする

電話をかけるSystemNavigator.pop()ダーツコードから 次の iOS コードを呼び出します。

UIViewController* viewController = [UIApplication sharedApplication].keyWindow.rootViewController;
if ([viewController isKindOfClass:[UINavigationController class]]) {
  [((UINavigationController*)viewController) popViewControllerAnimated:NO];
}

これで希望どおりに動作しない場合は、独自のものを作成できますプラットフォームチャンネル任意の iOS コードを呼び出します。

ローカリゼーションの処理

iOS とは異なり、Localizable.stringsファイル、 Flutter には現在、文字列を処理するための専用システムがありません。 現時点でのベストプラクティスは、コピーテキストを宣言することです。 クラス内で静的フィールドとして保存し、そこからアクセスします。例えば:

class Strings {
  static const String welcomeMessage = 'Welcome To Flutter';
}

次のようにして文字列にアクセスできます。

Text(Strings.welcomeMessage);

デフォルトでは、Flutter は文字列として米国英語のみをサポートします。 他の言語のサポートを追加する必要がある場合は、 を含むflutter_localizationsパッケージ。 ダーツを追加する必要がある場合もありますintl日付/時刻のフォーマットなど、i10n 機構を使用するためのパッケージ。

dependencies:
  flutter_localizations:
    sdk: flutter
  intl: '^0.17.0'

を使用するには、flutter_localizationsパッケージ、 を指定しますlocalizationsDelegatessupportedLocalesアプリのウィジェットで:

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';

class MyWidget extends StatelessWidget {
  const MyWidget({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      localizationsDelegates: <LocalizationsDelegate<dynamic>>[
        // Add app-specific localization delegate[s] here
        GlobalMaterialLocalizations.delegate,
        GlobalWidgetsLocalizations.delegate,
      ],
      supportedLocales: <Locale>[
        Locale('en', 'US'), // English
        Locale('he', 'IL'), // Hebrew
        // ... other locales the app supports
      ],
    );
  }
}

デリゲートには実際のローカライズされた値が含まれています。 一方supportedLocalesアプリがサポートするロケールを定義します。 上記の例では、MaterialApp、 したがって、それは両方を持っていますGlobalWidgetsLocalizationsベースウィジェットのローカライズされた値の場合、 そしてMaterialWidgetsLocalizationsマテリアル ウィジェットのローカリゼーション用。 使用する場合WidgetsAppあなたのアプリでは後者は必要ありません。 これら 2 つのデリゲートには「デフォルト」値が含まれていることに注意してください。 ただし、1 人以上の代理人を指定する必要があります 独自のアプリのローカライズ可能なコピーの場合、 それらもローカライズしたい場合。

初期化すると、WidgetsApp(またMaterialApp) を作成しますLocalizationsあなたのためのウィジェット、 あなたが指定した代理人と一緒に。 デバイスの現在のロケールには常にアクセスできます からLocalizations現在のコンテキストからのウィジェット (の形で)Localeオブジェクト)、またはWindow.locale

ローカライズされたリソースにアクセスするには、Localizations.of()方法 特定のデリゲートによって提供される特定のローカリゼーション クラスにアクセスします。 使用intl_translation翻訳可能なコピーを抽出するためのパッケージ にアーブファイルを翻訳してアプリにインポートし直す 一緒に使用するためintl

Flutter の国際化とローカリゼーションの詳細については、 を参照してください国際化ガイド、サンプルコードがあります ありとなしintlパッケージ。

依存関係の管理

iOS では、CocoaPods との依存関係を追加します。Podfile。 Flutter は Dart のビルド システムと Pub パッケージ マネージャーを使用します 依存関係を処理するため。ツールは、 Android および iOS のネイティブ ラッパー アプリを それぞれのビルド システム。

iOS フォルダーに Podfile がある間、 Flutter プロジェクト。ネイティブを追加する場合にのみこれを使用します。 プラットフォームごとの統合に必要な依存関係。 一般に、使用しますpubspec.yamlFlutter で外部依存関係を宣言します。 Flutter の優れたパッケージを見つけるのに最適な場所は次のとおりです。パブ.dev。

ビューコントローラー

ドキュメントのこのセクションでは、同等のものについて説明します。 Flutter の ViewController の説明とリッスンする方法 ライフサイクルイベント。

Flutter の ViewController に相当

UIKit では、ViewControllerユーザーインターフェースの一部を表し、 最も一般的には画面またはセクションに使用されます。 これらを組み合わせて複雑なユーザー インターフェイスを構築します。 アプリケーションの UI の拡張に役立ちます。 Flutter では、この仕事はウィジェットに割り当てられます。 ナビゲーションセクションで述べたように、 Flutter の画面はウィジェットによって表されます。 「すべてがウィジェットだ!」 使うNavigator異なる間を移動するRoutes さまざまな画面またはページを表す あるいは、同じデータの状態やレンダリングが異なる場合もあります。

ライフサイクル イベントのリッスン

UIKit では、メソッドをオーバーライドできます。ViewControllerビュー自体のライフサイクル メソッドをキャプチャするには、 または、ライフサイクル コールバックをAppDelegate。 Flutter にはどちらの概念もありませんが、代わりに次のことができます。 にフックしてライフサイクル イベントをリッスンする のWidgetsBinding観察者と聞く者 のdidChangeAppLifecycleState()変更イベント。

監視可能なライフサイクル イベントは次のとおりです。

inactive
アプリケーションは非アクティブな状態にあり、受信していません ユーザー入力。このイベントは iOS でのみ機能します。 Android には同等のイベントがないためです。
paused
アプリケーションは現在ユーザーには表示されませんが、 ユーザー入力に応答せず、バックグラウンドで実行されています。
resumed
アプリケーションは表示され、ユーザー入力に応答します。
suspending
アプリケーションは一時停止されています。 iOS プラットフォームには同等のイベントがありません。

これらの状態の意味の詳細については、「」を参照してください。AppLifecycleStateドキュメンテーション。

レイアウト

このセクションでは、Flutter のさまざまなレイアウトについて説明します そしてUIKitとの比較。

リストビューの表示

UIKit では、次のリストを表示できます。 どちらかUITableViewまたはUICollectionView。 Flutter では、ListView。 UIKit では、これらのビューにはデリゲート メソッドがあります 行数を決定するため、 各インデックス パスのセルとセルのサイズ。

Flutter の不変ウィジェット パターンにより、 ウィジェットのリストをListView、 Flutter は次のことを確認します。 スクロールは速くてスムーズです。

import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> _getListData() {
    final List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(Padding(
        padding: const EdgeInsets.all(10),
        child: Text('Row $i'),
      ));
    }
    return widgets;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: ListView(children: _getListData()),
    );
  }
}

クリックされた内容の検出

UIKit では、デリゲート メソッドを実装します。tableView:didSelectRowAtIndexPath:。 Flutter では、渡されたウィジェットによって提供されるタッチ処理を使用します。

import 'dart:developer' as developer;
import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> _getListData() {
    List<Widget> widgets = [];
    for (int i = 0; i < 100; i++) {
      widgets.add(
        GestureDetector(
          onTap: () {
            developer.log('row tapped');
          },
          child: Padding(
            padding: const EdgeInsets.all(10),
            child: Text('Row $i'),
          ),
        ),
      );
    }
    return widgets;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: ListView(children: _getListData()),
    );
  }
}

ListView の動的更新

UIKit では、リスト ビューのデータを更新します。 を使用してテーブルまたはコレクション ビューに通知します。reloadData方法。

Flutter 内でウィジェットのリストを更新すると、setState()、 データが視覚的に変化していないことがすぐにわかります。 なぜなら、いつsetState()と呼ばれます、 Flutter レンダリング エンジンはウィジェット ツリーを確認します。 何かが変わったかどうかを確認します。 それがあなたの手元に届いたら、ListViewを実行します。==チェック、 そして、その2つがListViewも同じです。 何も変更されていないため、更新する必要はありません。

を更新する簡単な方法については、ListView、 新しいを作成しますListの中にsetState()、 古いリストから新しいリストにデータをコピーします。 このアプローチはシンプルですが、 大規模なデータセットには推奨されません。 次の例に示すように。

import 'dart:developer' as developer;

import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = <Widget>[];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  Widget getRow(int i) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets = List.from(widgets);
          widgets.add(getRow(widgets.length));
          developer.log('row $i');
        });
      },
      child: Padding(
        padding: const EdgeInsets.all(10),
        child: Text('Row $i'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: ListView(children: widgets),
    );
  }
}

推奨される、効率的な、 リストを作成する効果的な方法は、ListView.Builder。 この方法は、動的なイベントがある場合に最適です。 リストまたは非常に大量のデータを含むリスト。

import 'dart:developer' as developer;

import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Widget> widgets = [];

  @override
  void initState() {
    super.initState();
    for (int i = 0; i < 100; i++) {
      widgets.add(getRow(i));
    }
  }

  Widget getRow(int i) {
    return GestureDetector(
      onTap: () {
        setState(() {
          widgets.add(getRow(widgets.length));
          developer.log('row $i');
        });
      },
      child: Padding(
        padding: const EdgeInsets.all(10),
        child: Text('Row $i'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: ListView.builder(
        itemCount: widgets.length,
        itemBuilder: (context, position) {
          return getRow(position);
        },
      ),
    );
  }
}

を作成する代わりに、ListViewを作成します。ListView.builderこれは 2 つの重要なパラメータを取ります: リストの初期長、 とItemBuilder関数。

ItemBuilder関数は次のようなものですcellForItemAtiOS テーブルまたはコレクション ビューのデリゲート メソッド、 ポジションを取得し、 その位置にレンダリングしたいセル。

最後に、しかし最も重要なことですが、onTap()関数 リストはもう再作成されませんが、代わりに.addそれには。

スクロールビューの作成

UIKit では、ビューをScrollViewそれか 必要に応じてユーザーがコンテンツをスクロールできるようにします。

Flutter でこれを行う最も簡単な方法は、ListViewウィジェット。 これは両方の機能として機能します。ScrollViewそしてiOSTableView、 ウィジェットを垂直形式でレイアウトできるためです。

@override
Widget build(BuildContext context) {
  return ListView(
    children: const <Widget>[
      Text('Row One'),
      Text('Row Two'),
      Text('Row Three'),
      Text('Row Four'),
    ],
  );
}

Flutter でウィジェットをレイアウトする方法に関する詳細なドキュメントについては、 を参照してくださいレイアウトのチュートリアル

ジェスチャー検出とタッチイベント処理

このセクションでは、ジェスチャを検出する方法について説明します Flutter でさまざまなイベントを処理します。 そしてUIKitとの比較。

クリックリスナーの追加

UIKit では、GestureRecognizerビューへ クリックイベントを処理します。 Flutter では、タッチ リスナーを追加する方法が 2 つあります。

  1. ウィジェットがイベント検出をサポートしている場合は、ウィジェットに関数を渡します。 そして関数内でイベントを処理します。たとえば、ElevatedButtonウィジェットにはonPressedパラメータ:

    @override
    Widget build(BuildContext context) {
      return ElevatedButton(
        onPressed: () {
          developer.log('click');
        },
        child: const Text('Button'),
      );
    }
  2. ウィジェットがイベント検出をサポートしていない場合は、 GestureDetector でウィジェットをラップし、関数を渡します にonTapパラメータ。

    class SampleTapApp extends StatelessWidget {
      const SampleTapApp({super.key});
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          body: Center(
            child: GestureDetector(
              onTap: () {
                developer.log('tap');
              },
              child: const FlutterLogo(
                size: 200,
              ),
            ),
          ),
        );
      }
    }

他のジェスチャの処理

使用するGestureDetector聞くことができます 次のような幅広いジェスチャに対応します。

  • タッピング

    onTapDown
    タップの原因となる可能性のあるポインタが接触しました。 特定の場所の画面。
    onTapUp
    タップをトリガーするポインタが接触を停止しました。 特定の場所の画面。
    onTap
    タップが発生しました。
    onTapCancel
    以前にトリガーしたポインターonTapDownタップの原因にはなりません。
  • ダブルタップ

    onDoubleTap
    ユーザーが画面の同じ場所を 2 回タップしました。 素早い連続。
  • 長押し

    onLongPress
    ポインターが画面に触れたままになっている 長時間同じ場所にいること。
  • 垂直方向のドラッグ

    onVerticalDragStart
    ポインタが画面に触れ、動き始める可能性があります。 垂直に移動します。
    onVerticalDragUpdate
    画面に接触しているポインタ さらに垂直方向に移動しました。
    onVerticalDragEnd
    以前に接触していたポインタ 画面と垂直方向の移動が接触しなくなりました 画面とともに特定の速度で移動していた 画面に接触しなくなったとき。
  • 水平方向のドラッグ

    onHorizontalDragStart
    ポインタが画面に接触し、開始する可能性があります 水平方向に移動します。
    onHorizontalDragUpdate
    画面に接触しているポインタ さらに水平方向に移動しました。
    onHorizontalDragEnd
    以前に接触していたポインタ 画面と水平方向の移動はなくなりました 画面との接触。

次の例は、GestureDetectorダブルタップで Flutter ロゴを回転させます。

class SampleApp extends StatefulWidget {
  const SampleApp({super.key});

  @override
  State<SampleApp> createState() => _SampleAppState();
}

class _SampleAppState extends State<SampleApp>
    with SingleTickerProviderStateMixin {
  late AnimationController controller;
  late CurvedAnimation curve;

  @override
  void initState() {
    super.initState();
    controller = AnimationController(
      vsync: this,
      duration: const Duration(milliseconds: 2000),
    );
    curve = CurvedAnimation(
      parent: controller,
      curve: Curves.easeIn,
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: GestureDetector(
          onDoubleTap: () {
            if (controller.isCompleted) {
              controller.reverse();
            } else {
              controller.forward();
            }
          },
          child: RotationTransition(
            turns: curve,
            child: const FlutterLogo(
              size: 200,
            ),
          ),
        ),
      ),
    );
  }
}

テーマ、スタイル、メディア

Flutter アプリケーションはスタイル設定が簡単です。切り替えることができます 明るいテーマと暗いテーマの間で、 テキストと UI コンポーネントのスタイルを変更します。 もっと。このセクションでは、Flutter アプリのスタイル設定の側面について説明します そして、UIKit で同じことを行う方法を比較します。

テーマの使用

Flutter には、すぐに使える美しい実装が付属しています。 マテリアル デザインの多くのスタイリングと 通常行うテーマのニーズ。

アプリでマテリアル コンポーネントを最大限に活用するには、 最上位のウィジェットを宣言し、MaterialApp、 アプリケーションへのエントリ ポイントとして。MaterialApp数値をラップする便利なウィジェットです アプリケーションに一般的に必要なウィジェットの一覧 マテリアルデザインを実装しています。 それは、WidgetsAppマテリアル固有の機能を追加することによって。

しかし、Flutter は実装するのに十分な柔軟性と表現力があります。 あらゆるデザイン言語。 iOS では、クパチーノ図書館に準拠したインターフェイスを作成するには、ヒューマンインターフェースガイドライン。 これらのウィジェットの完全なセットについては、 を参照してくださいクパチーノのウィジェットギャラリー。

を使用することもできますWidgetsAppアプリのウィジェットとして、 同じ機能の一部を提供しますが、 しかし、それほど裕福ではありませんMaterialApp

子コンポーネントの色とスタイルをカスタマイズするには、 渡すThemeDataに反対するMaterialAppウィジェット。 たとえば、以下のコードでは、 プライマリ スウォッチは青に設定され、ディバイダの色はグレーに設定されます。

import 'package:flutter/material.dart';

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Sample App',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        dividerColor: Colors.grey,
      ),
      home: const SampleAppPage(),
    );
  }
}

カスタムフォントの使用

UIKit では、任意のものをインポートしますttfフォント ファイルをプロジェクトに追加する そして参照を作成しますinfo.plistファイル。 Flutterでフォントファイルをフォルダに置きます そしてそれを参照してくださいpubspec.yamlファイル、 画像をインポートする方法と同様です。

fonts:
  - family: MyCustomFont
    fonts:
      - asset: fonts/MyCustomFont.ttf
      - style: italic

次にフォントをTextウィジェット:

@override
Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('Sample App'),
    ),
    body: const Center(
      child: Text(
        'This is a custom font text',
        style: TextStyle(fontFamily: 'MyCustomFont'),
      ),
    ),
  );
}

テキストのスタイル設定

フォントに加えて、他のスタイル要素もカスタマイズできます。Textウィジェット。 のスタイルパラメータTextウィジェットはTextStyle物体、 ここでは、次のような多くのパラメータをカスタマイズできます。

  • color
  • decoration
  • decorationColor
  • decorationStyle
  • fontFamily
  • fontSize
  • fontStyle
  • fontWeight
  • hashCode
  • height
  • inherit
  • letterSpacing
  • textBaseline
  • wordSpacing

アプリ内に画像をバンドルする

iOS は画像とアセットを別個のアイテムとして扱いますが、 Flutter アプリにはアセットのみがあります。リソースとは、 に置かれたImages.xcassetiOSのフォルダー、 Flutter のアセットフォルダーに配置されます。 iOS と同様に、アセットは画像だけでなく、あらゆる種類のファイルです。 たとえば、JSON ファイルが次の場所にあるとします。my-assetsフォルダ:

my-assets/data.json

で資産を宣言します。pubspec.yamlファイル:

assets:
 - my-assets/data.json

そして、次のコマンドを使用してコードからアクセスします。AssetBundle:

import 'dart:async' show Future;
import 'package:flutter/services.dart' show rootBundle;

Future<String> loadAsset() async {
  return await rootBundle.loadString('my-assets/data.json');
}

画像の場合、Flutter は iOS のような単純な密度ベースの形式に従います。 画像アセットは次のとおりです。1.0x2.0x3.0x、またはその他の乗数。 flutterズdevicePixelRatio比率を表します 単一の論理ピクセル内の物理ピクセルの数。

アセットは任意のフォルダーにあります。 Flutter には事前定義されたフォルダー構造はありません。 資産を(場所とともに)宣言します。 のpubspec.yamlファイルを開くと、Flutter がそれらを取得します。

たとえば、という名前の画像を追加するには、my_icon.pngあなたのときめきに プロジェクトの場合は、任意の名前のフォルダーに保存することもできます。images。 ベース画像(1.0x)をimagesフォルダー、および 適切な比率乗数にちなんで名付けられたサブフォルダー内の他のバリアント:

images/my_icon.png       // Base: 1.0x image
images/2.0x/my_icon.png  // 2.0x image
images/3.0x/my_icon.png  // 3.0x image

次に、これらのイメージをpubspec.yamlファイル:

assets:
 - images/my_icon.png

次を使用して画像にアクセスできるようになりましたAssetImage:

AssetImage('images/a_dot_burr.jpeg')

または直接Imageウィジェット:

@override
Widget build(BuildContext context) {
  return Image.asset('images/my_image.png');
}

詳細については、を参照してください。Flutter でのアセットと画像の追加

フォーム入力

このセクションでは、Flutter でフォームを使用する方法について説明します。 そしてUIKitとの比較。

ユーザー入力の取得

Flutter が別の状態を持つ不変ウィジェットをどのように使用するかを考えると、 ユーザー入力がどのように画像に反映されるのか疑問に思われるかもしれません。 UIKit では通常、ウィジェットの現在の値をクエリします。 ユーザー入力を送信するとき、またはそれに対するアクションを送信するとき。 Flutter ではどのように機能するのでしょうか?

実際には、フォームは Flutter のすべてと同様に処理されます。 特殊なウィジェットを使用します。持っている場合は、TextFieldまたはTextFormFieldを指定できます。TextEditingControllerユーザー入力を取得するには:

class _MyFormState extends State<MyForm> {
  // Create a text controller and use it to retrieve the current value.
  // of the TextField!
  final myController = TextEditingController();

  @override
  void dispose() {
    // Clean up the controller when disposing of the Widget.
    myController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Retrieve Text Input')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: TextField(controller: myController),
      ),
      floatingActionButton: FloatingActionButton(
        // When the user presses the button, show an alert dialog with the
        // text the user has typed into our text field.
        onPressed: () {
          showDialog(
            context: context,
            builder: (context) {
              return AlertDialog(
                // Retrieve the text the user has typed in using our
                // TextEditingController.
                content: Text(myController.text),
              );
            },
          );
        },
        tooltip: 'Show me the value!',
        child: const Icon(Icons.text_fields),
      ),
    );
  }
}

詳細と完全なコードのリストは、次の場所にあります。テキストフィールドの値を取得する、 から flutterクックブック

テキストフィールドのプレースホルダー

Flutter では、「ヒント」またはプレースホルダー テキストを簡単に表示できます フィールドにInputDecoration物体 の装飾コンストラクターパラメータにTextウィジェット:

Center(
  child: TextField(
    decoration: InputDecoration(hintText: 'This is a hint'),
  ),
)

検証エラーの表示

「ヒント」と同じように、InputDecoration物体 の装飾コンストラクターにTextウィジェット。

ただし、最初からエラーを表示することは望ましくありません。 代わりに、ユーザーが無効なデータを入力した場合、 状態を更新し、新しい値を渡しますInputDecoration物体。

import 'package:flutter/material.dart';

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  String? _errorText;

  bool isEmail(String em) {
    String emailRegexp =
        r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|'
        r'(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|'
        r'(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$';

    RegExp regExp = RegExp(emailRegexp);

    return regExp.hasMatch(em);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: Center(
        child: TextField(
          onSubmitted: (text) {
            setState(() {
              if (!isEmail(text)) {
                _errorText = 'Error: This is not an email';
              } else {
                _errorText = null;
              }
            });
          },
          decoration: InputDecoration(
            hintText: 'This is a hint',
            errorText: _errorText,
          ),
        ),
      ),
    );
  }
}

スレッド化と非同期性

このセクションでは、Flutter と UIKitとの比較。

非同期コードの作成

Dart にはシングルスレッド実行モデルがあり、 のサポート付きIsolates (別のスレッドで Dart コードを実行する方法)、 イベントループと非同期プログラミング。 あなたがスポーンしない限り、Isolate、 Dart コードはメイン UI スレッドで実行され、 イベントループによって駆動されます。 Flutterのイベントループは iOS のメイン ループに相当します。つまり、 のLooperそれはメインスレッドに接続されています。

Dart のシングルスレッド モデルは、あなたがそうであることを意味するものではありません すべてをブロック操作として実行するために必要 これにより UI がフリーズします。その代わり、 Dart 言語が提供する非同期機能を使用します。 そのようなasync/await、非同期作業を実行します。

たとえば、ネットワーク コードを実行しても、 を使用してハングする UIasync/awaitそしてダーツにやらせる 重労働:

Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = jsonDecode(response.body);
  });
}

一度awaitネットワーク通話が完了し、 呼び出して UI を更新するsetState()、 これにより、ウィジェットのサブツリーの再構築がトリガーされます。 そしてデータを更新します。

次の例では、データを非同期的にロードし、 で表示しますListView:

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, dynamic>> data = <Map<String, dynamic>>[];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  Future<void> loadData() async {
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    setState(() {
      data = jsonDecode(response.body);
    });
  }

  Widget getRow(int index) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text('Row ${data[index]['title']}'),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: ListView.builder(
        itemCount: data.length,
        itemBuilder: (context, index) {
          return getRow(index);
        },
      ),
    );
  }
}

作業の詳細については、次のセクションを参照してください。 バックグラウンドでの動作と、Flutter が iOS とどのように異なるのかを説明します。

バックグラウンドスレッドへの移動

Flutterはシングルスレッドでイベントループを実行するため、 (Node.js のように)、心配する必要はありません。 スレッド管理またはバックグラウンド スレッドの生成。 I/O バウンドの作業を行っている場合は、 ディスクアクセスやネットワーク通話など、 そうすれば安全に使用できますasync/awaitこれで完了です。 一方、大量の計算を行う必要がある場合は、 CPU をビジー状態に保つ作業がある場合は、CPU を別の場所に移動したいとします。Isolateイベントループのブロックを避けるため。

I/O バウンドの作業の場合は、関数を次のように宣言します。async関数、 とawait関数内の長時間実行タスクの場合:

Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = jsonDecode(response.body);
  });
}

これは通常、ネットワークまたはデータベース呼び出しを行う方法です。 どちらも I/O 操作です。

ただし、処理している場合があります。 大量のデータがあり、UI がハングします。 Flutterでは、使用しますIsolateを活用する 複数の CPU コアを使用して長時間実行するか、 計算負荷の高いタスク。

アイソレートは、共有しない個別の実行スレッドです。 メイン実行メモリ ヒープを持つ任意のメモリ。 これは、メインスレッドから変数にアクセスできないことを意味します。 または呼び出して UI を更新しますsetState()。 アイソレートはその名の通り、記憶を共有できない (たとえば、静的フィールドの形式で)。

次の例は、単純な分離で次のことを示しています。 UI を更新するためにメインスレッドにデータを共有する方法。

Future<void> loadData() async {
  final ReceivePort receivePort = ReceivePort();
  await Isolate.spawn(dataLoader, receivePort.sendPort);

  // The 'echo' isolate sends its SendPort as the first message.
  final SendPort sendPort = await receivePort.first as SendPort;

  final List<Map<String, dynamic>> msg = await sendReceive(
    sendPort,
    'https://jsonplaceholder.typicode.com/posts',
  );

  setState(() {
    data = msg;
  });
}

// The entry point for the isolate.
static Future<void> dataLoader(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages.
  final ReceivePort port = ReceivePort();

  // Notify any other isolates what port this isolate listens to.
  sendPort.send(port.sendPort);

  await for (final dynamic msg in port) {
    final String url = msg[0] as String;
    final SendPort replyTo = msg[1] as SendPort;

    final Uri dataURL = Uri.parse(url);
    final http.Response response = await http.get(dataURL);
    // Lots of JSON to parse
    replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
  }
}

Future<List<Map<String, dynamic>>> sendReceive(SendPort port, String msg) {
  final ReceivePort response = ReceivePort();
  port.send(<dynamic>[msg, response.sendPort]);
  return response.first as Future<List<Map<String, dynamic>>>;
}

ここ、dataLoader()それはIsolateそれが流れ込む 独自の別個の実行スレッド。 分離では、より多くの CPU を使用する実行が可能になります 処理 (大きな JSON の解析など)、 または、計算量の多い数学を実行する場合、 暗号化や信号処理など。

以下の完全な例を実行できます。

import 'dart:async';
import 'dart:convert';
import 'dart:isolate';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, dynamic>> data = <Map<String, dynamic>>[];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  bool get showLoadingDialog => data.isEmpty;

  Future<void> loadData() async {
    final ReceivePort receivePort = ReceivePort();
    await Isolate.spawn(dataLoader, receivePort.sendPort);

    // The 'echo' isolate sends its SendPort as the first message.
    final SendPort sendPort = await receivePort.first as SendPort;

    final List<Map<String, dynamic>> msg = await sendReceive(
      sendPort,
      'https://jsonplaceholder.typicode.com/posts',
    );

    setState(() {
      data = msg;
    });
  }

  // The entry point for the isolate.
  static Future<void> dataLoader(SendPort sendPort) async {
    // Open the ReceivePort for incoming messages.
    final ReceivePort port = ReceivePort();

    // Notify any other isolates what port this isolate listens to.
    sendPort.send(port.sendPort);

    await for (final dynamic msg in port) {
      final String url = msg[0] as String;
      final SendPort replyTo = msg[1] as SendPort;

      final Uri dataURL = Uri.parse(url);
      final http.Response response = await http.get(dataURL);
      // Lots of JSON to parse
      replyTo.send(jsonDecode(response.body) as List<Map<String, dynamic>>);
    }
  }

  Future<List<Map<String, dynamic>>> sendReceive(SendPort port, String msg) {
    final ReceivePort response = ReceivePort();
    port.send(<dynamic>[msg, response.sendPort]);
    return response.first as Future<List<Map<String, dynamic>>>;
  }

  Widget getBody() {
    bool showLoadingDialog = data.isEmpty;

    if (showLoadingDialog) {
      return getProgressDialog();
    } else {
      return getListView();
    }
  }

  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }

  ListView getListView() {
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, position) {
        return getRow(position);
      },
    );
  }

  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${data[i]["title"]}"),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: getBody(),
    );
  }
}

ネットワークリクエストを行う

Flutter でネットワーク呼び出しを行うのは簡単です。 人気のあるものを使用するhttpパッケージ。この要約は 通常ならできるネットワークの多くを切り離す 自分で実装すると、ネットワーク呼び出しが簡単になります。

追加するには、httpパッケージを依存関係として実行しますflutter pub add:

$ flutter pub add http

ネットワーク通話を行うには、 電話awaitasync関数http.get():

Future<void> loadData() async {
  final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
  final http.Response response = await http.get(dataURL);
  setState(() {
    data = jsonDecode(response.body);
  });
}

長時間実行されるタスクの進行状況を表示する

UIKit では通常、UIProgressViewバックグラウンドで長時間実行されるタスクを実行しているとき。

Flutter では、ProgressIndicatorウィジェット。 プログラムで制御して進行状況を表示する ブール値フラグを介してレンダリングされるとき。 長時間実行されるタスクが開始される前に状態を更新するように Flutter に指示します。 そして終了後は非表示にします。

以下の例では、ビルド関数が 3 つの異なる関数に分かれています。 機能。もしもshowLoadingDialogtrue(いつwidgets.length == 0)、レンダリングします。ProgressIndicator。 それ以外の場合は、レンダリングListViewネットワーク呼び出しから返されたデータを使用します。

import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const SampleApp());
}

class SampleApp extends StatelessWidget {
  const SampleApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: 'Sample App',
      home: SampleAppPage(),
    );
  }
}

class SampleAppPage extends StatefulWidget {
  const SampleAppPage({super.key});

  @override
  State<SampleAppPage> createState() => _SampleAppPageState();
}

class _SampleAppPageState extends State<SampleAppPage> {
  List<Map<String, dynamic>> data = <Map<String, dynamic>>[];

  @override
  void initState() {
    super.initState();
    loadData();
  }

  bool get showLoadingDialog => data.isEmpty;

  Future<void> loadData() async {
    final Uri dataURL = Uri.parse('https://jsonplaceholder.typicode.com/posts');
    final http.Response response = await http.get(dataURL);
    setState(() {
      data = jsonDecode(response.body);
    });
  }

  Widget getBody() {
    if (showLoadingDialog) {
      return getProgressDialog();
    }

    return getListView();
  }

  Widget getProgressDialog() {
    return const Center(child: CircularProgressIndicator());
  }

  ListView getListView() {
    return ListView.builder(
      itemCount: data.length,
      itemBuilder: (context, index) {
        return getRow(index);
      },
    );
  }

  Widget getRow(int i) {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: Text("Row ${data[i]["title"]}"),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Sample App'),
      ),
      body: getBody(),
    );
  }
}